package org.elsfs.auth.config;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import org.elsfs.framework.generator.SnowFlake;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.RequestMatcher;

/**
 * oauth2配置
 *
 * @author zeng
 * @since 0.0.1
 */
@Configuration
public class AuthorizationConfig {

  public static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";

  @Value("${elsfs.security.public-key-path}")
  RSAPublicKey publicKey;

  @Value("${elsfs.security.private-key-path}")
  RSAPrivateKey privateKey;

  private final OAuth2AuthorizationServerProperties oauth2AuthorizationServerProperties;

  /**
   * 构造方法
   *
   * @param oauth2AuthorizationServerProperties 配置参数
   */
  public AuthorizationConfig(
      OAuth2AuthorizationServerProperties oauth2AuthorizationServerProperties) {
    this.oauth2AuthorizationServerProperties = oauth2AuthorizationServerProperties;
  }

  /**
   * 过滤器链配置
   *
   * @param http http
   * @return SecurityFilterChain
   * @throws Exception e
   */
  @Bean
  @Order(Ordered.HIGHEST_PRECEDENCE)
  public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
      throws Exception {
    OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
        new OAuth2AuthorizationServerConfigurer();
    authorizationServerConfigurer
        .authorizationEndpoint(
            authorizationEndpoint -> authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI))
        .oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0  }
    RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
    http.securityMatcher(endpointsMatcher)
        .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
        .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
        .exceptionHandling(
            exceptions ->
                exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")))
        .oauth2ResourceServer(
            oauth2ResourceServer -> oauth2ResourceServer.jwt(Customizer.withDefaults()))
        .apply(authorizationServerConfigurer);
    return http.build();
  }

  /**
   * 实例 RegisteredClient
   *
   * @return RegisteredClient
   */
  public static RegisteredClient registeredClientRepository() {
    RegisteredClient registeredClient =
        RegisteredClient.withId(new SnowFlake(31).nextId() + "")
            .clientId("messaging-client")
            .clientSecret("{noop}secret")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
            .redirectUri("http://127.0.0.1:8080/authorized")
            .scope(OidcScopes.OPENID)
            .scope(OidcScopes.PROFILE)
            .scope("message.read")
            .scope("message.write")
            .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
            .build();
    return registeredClient;
  }

  /**
   * AuthorizationServerSettings路径配置
   *
   * @return AuthorizationServerSettings
   */
  @Bean
  public AuthorizationServerSettings authorizationServerSettings() {
    return AuthorizationServerSettings.builder()
        .issuer(oauth2AuthorizationServerProperties.getIssuer())
        .build();
  }

  /**
   * 授权同意配置
   *
   * @return oauth2
   */
  @Bean
  public OAuth2AuthorizationConsentService authorizationConsentService() {
    // Will be used by the ConsentController
    return new InMemoryOAuth2AuthorizationConsentService();
  }

  /**
   * jwkSource配置
   *
   * @return jwkSource
   */
  @Bean
  public JWKSource<SecurityContext> jwkSource() {
    // @formatter:off
    RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).keyID("123").build();
    // @formatter:on
    JWKSet jwkSet = new JWKSet(rsaKey);
    return new ImmutableJWKSet<>(jwkSet);
  }
}
